﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceModel;
using System.Text;
using System.Threading;
using System.Xml.Linq;
using BlueCurve.Common;
using BlueCurve.Common.Application;
using BlueCurve.WcfServices;
using Scheduler.Remoting;

namespace Scheduler
{
    class Program : BlueCurve.Common.InteractiveConsole.BaseConsole
    {
        /// <summary>
        /// Permet de laisse tourner le thread
        /// de gestion des hotes en attentes
        /// </summary>
        private static bool s_IsRun = true;
        /// <summary>
        /// Temps d'attente avant de recontacter un serveur
        /// </summary>
        private static int s_HostWaitTime = Convert.ToInt32(ConfigurationManager.AppSettings["HostTimeout"]);
        /// <summary>
        /// Objet de verrouillage des threads
        /// </summary>
        private static readonly object s_ThreadsLock = new object();
        /// <summary>
        /// Objet de verrouillage des threads
        /// </summary>
        private static readonly object s_ThreadsLock2 = new object();
        /// <summary>
        /// Thread de traitement des hotes
        /// en attente
        /// </summary>
        private static Thread s_checkdatabase { get; set; }
        /// <summary>
        /// Thread de la console interactive
        /// </summary>
        private static Thread s_console { get; set; }





        #region 'Functions'

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            Console.WriteLine("Wait while loading datas to database for optimal crawl");
            BootLoader();
            Console.WriteLine("Datas loading is finish");

            s_console = new Thread(new ThreadStart(new Program().Run));
            s_checkdatabase = new Thread(new ThreadStart(CheckDatabase));
            s_console.Start();
            s_checkdatabase.Start();

            Tcpservice._ChangeHostWaitTime += new Tcpservice.ChangeHostWaitTimeEventHandler(Tcpservice_Changes_HostWaitTime);
            Tcpservice._GetAllPriority += new Tcpservice.GetAllPriorityEventHandler(Tcpservice_GetAllPriority);
            Tcpservice._GetPriority += new Tcpservice.GetPriorityEventHandler(Tcpservice_GetPriority);
            Tcpservice._LinkToUpdate += new Tcpservice.LinkToUpdateEventHandler(Tcpservice_LinkToUpdate);
            Tcpservice._SetPriority += new Tcpservice.SetPriorityEventHandler(Tcpservice_SetPriority);
            Tcpservice._StopApplication += new EventHandler(Tcpservice_StopApplication);
            Tcpservice._RestartApplication += new EventHandler(Tcpservice_RestartApplication);

            // Demarre le service wcf
            Host.StartService<Scheduler.Remoting.Tcpservice>();
        }


        /// <summary>
        /// Redefinit la priorité d'un élément
        /// </summary>
        private static void Tcpservice_SetPriority(object sender, Eventdata e)
        {
            lock (s_ThreadsLock)
            {
                using (SchedulerdbDataContext database = new SchedulerdbDataContext())
                {
                    database.ChangePriority(e.link, e.priority);
                }
            }
        }

        /// <summary>
        /// Ajoute de nouveaux fichiers à mettre à jour dans la base de données
        /// </summary>
        private static void Tcpservice_LinkToUpdate(object sender, Eventdata e)
        {
            CommonFunctions common = new CommonFunctions();
            try
            {
                if (!string.IsNullOrEmpty(e.links))
                    return;

                XDocument doc = XDocument.Load(new StringReader(e.links));

                var x = from i in doc.Descendants()
                        select new
                        {
                            Url = i.Element("url").Value,
                            State = Convert.ToInt32(i.Element("state").Value),
                            Lastupdate = DateTime.Parse(i.Element("lastupdate").Value),
                            Lastmodified = DateTime.Parse(i.Element("lastmodified").Value),
                            Reindextime = Convert.ToInt32(i.Element("reindextime").Value),
                            Priority = Convert.ToInt32(i.Element("priority").Value)
                        };
                using (SchedulerdbDataContext database = new SchedulerdbDataContext())
                {
                    foreach (var v in x)
                    {
                        database.AddDocumentToUpdateQueue(common.NormalizeLink(v.Url), v.Lastupdate, v.Lastmodified, v.Reindextime, v.Priority);
                    }
                }
            }
            catch
            {
                // Données corrompu
            }
        }

        /// <summary>
        /// Retourne la priotiré d'un lien
        /// </summary>
        /// <returns>Priorité de l'élement</returns>
        private static int Tcpservice_GetPriority(object sender, Eventdata e)
        {
            lock (s_ThreadsLock)
            {
                try
                {
                    using (SchedulerdbDataContext database = new SchedulerdbDataContext())
                    {
                        return database.GetPriority(e.link);
                    }
                }
                catch
                {
                    return -1;
                }
            }
        }

        /// <summary>
        /// Retourne un vue XML des priorités de chaque 
        /// lien dans la base de données
        /// </summary>
        /// <returns>Vue xml</returns>
        private static string Tcpservice_GetAllPriority(object sender, EventArgs e)
        {
            lock (s_ThreadsLock2)
            {
                StringBuilder strb = new StringBuilder();
                try
                {
                    using (SchedulerdbDataContext database = new SchedulerdbDataContext())
                    {
                        var x = database.GetAllPriority().ToDictionary(p => p.LINK);

                        if (x.Count == 0)
                            return null;

                        List<object> items = new List<object>();
                        foreach (var c in x)
                        {
                            items.Add(new XElement("node",
                                          new XElement("link",
                                              new XCData(c.Key)),
                                          new XElement("priority", c.Value.PRIORITY)));
                        }

                        XElement xml = new XElement("node", items.ToArray());
                        xml.Save(new StringWriter(strb));
                        items.Clear();
                    }
                    return strb.ToString();
                }
                catch
                {
                    return null;
                }
            }
        }

        /// <summary>
        /// Change le temps d'attente avant de recontacter un serveur
        /// </summary>
        private static void Tcpservice_Changes_HostWaitTime(object sender, Eventdata e)
        {
            if (e.waitime != s_HostWaitTime)
                Interlocked.Exchange(ref s_HostWaitTime, e.waitime);
        }
        
        #endregion



        #region 'Loader'
        
        /// <summary>
        /// Effectue diverses opération sur la base de données
        /// </summary>
        private static void CheckDatabase()
        {
            while (s_IsRun)
            {
                using (SchedulerdbDataContext database = new SchedulerdbDataContext())
                {
                    // Transfert des hotes de la WaitQueue vers la ReadyQueue
                    database.BackHostToReadyQueue(s_HostWaitTime);
                    // Suppression des hotes mort de la db
                    database.DeleteDeadHost();
                }
                if (!s_IsRun)
                    break;
                System.Threading.Thread.Sleep(5 * 60 * 1000); // 5 minutes
            }
        }

        /// <summary>
        /// Charge les données nécessaire
        /// au fonctionnement optimal
        /// </summary>
        private static void BootLoader()
        {
            int speed = Convert.ToInt32(ConfigurationManager.AppSettings["NetWorkSpeed"]);
            int timeout = Convert.ToInt32(ConfigurationManager.AppSettings["HostTimeout"]);
            Tcpclient client = new Tcpclient();
            CommonFunctions common = new CommonFunctions();

            using (SchedulerdbDataContext db = new SchedulerdbDataContext())
            {
                // Nombre d'hotes
                Nullable<int> AvailableHosts = db.CountAvailableHosts();
                if (AvailableHosts.HasValue)
                {
                    while (AvailableHosts.Value < speed * timeout)
                    {
                        string file = client.GetFileToParse();

                        if (!string.IsNullOrEmpty(file))
                        {
                            XDocument doc = XDocument.Load(file);

                            var x = from i in doc.Elements()
                                    select new
                                    {
                                        Urlbase = i.Element("urlbase").Value,
                                        Uris = (from e in i.Descendants("urls") select e.Element("uri").Value).ToList()
                                    };

                            foreach (var T in x)
                            {
                                string domain = common.GetDomainFromLink(common.NormalizeLink(T.Urlbase));
                                
                                db.AddHostToHostQueue(domain);
                                Nullable<int> hqid = db.GetHostIdFromHostQueueByHost(domain);
                                if (hqid.HasValue)
                                {
                                    T.Uris.ForEach(
                                        delegate(string link)
                                        {
                                            db.AddNewHostToDictionnary(hqid.Value, common.NormalizeLink(link));
                                        });
                                    db.AddHostToReadyQueue(hqid);
                                }

                                db.Dispose();
                            }
                        }

                        // Nombre d'hotes
                        AvailableHosts = db.CountAvailableHosts();

                        Thread.Sleep(150);
                    }
                }
            }
        }

        #endregion



        #region 'Console'
        
        /// <summary>
        /// Redemarre l'application
        /// </summary>
        private static void Tcpservice_RestartApplication(object sender, EventArgs e)
        {
            s_IsRun = false;
            // Enregistre l'application pour le redemarrage
            Restart.RestartApplication(string.Concat(System.Diagnostics.Process.GetCurrentProcess().ProcessName, ".exe"));
            // Fermeture du service wcf
            Host.StopService();

            if (s_checkdatabase.ThreadState == System.Threading.ThreadState.Running)
                s_checkdatabase.Abort();

            // Enregistre l'application pour le redemarrage
            Restart.RestartApplication(string.Concat(System.Diagnostics.Process.GetCurrentProcess().ProcessName, ".exe"));
            // Fermeture du service wcf
            Host.StopService();
            // Suicide de l'application
            Process.GetCurrentProcess().Kill();

        }

        /// <summary>
        /// Arrête l'application
        /// </summary>
        private static void Tcpservice_StopApplication(object sender, EventArgs e)
        {
            s_IsRun = false;
            if (s_checkdatabase.ThreadState == System.Threading.ThreadState.Running)
                s_checkdatabase.Abort();

            // Arret du service wcf
            Host.StopService();
            // Suicide de l'application
            Process.GetCurrentProcess().Kill();
        }

        /// <summary>
        /// Charge les commandes de la console
        /// </summary>
        protected override void LoadCommands()
        {
            this.Commands.Add("-restart",
                new Dictionary<Action, string>()
                {
                    { () => Tcpservice_RestartApplication("", EventArgs.Empty), "Redemarre l'application" }
                });
            this.Commands.Add("-stop",
                new Dictionary<Action, string>()
                {
                    { () => Tcpservice_StopApplication("", EventArgs.Empty), "Arrete l'application" }
                });
        }

        /// <summary>
        /// Retourne le nom de l'application
        /// </summary>
        public override string Name
        {
            get { return "BlueCurve Scheduler"; }
        }

        #endregion
    }
}